<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Form
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/** @see Zend_Validate_Interface */
require_once 'Zend/Validate/Interface.php';

/**
 * Zend_Form
 *
 * @category   Zend
 * @package    Zend_Form
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: Form.php 24156 2011-06-27 14:57:44Z ezimuel $
 */
class Zend_Form implements Iterator, Countable, Zend_Validate_Interface {
	/**#@+
	 * Plugin loader type constants
	 */
	const DECORATOR = 'DECORATOR';
	const ELEMENT = 'ELEMENT';
	/**#@-*/
	
	/**#@+
	 * Method type constants
	 */
	const METHOD_DELETE = 'delete';
	const METHOD_GET = 'get';
	const METHOD_POST = 'post';
	const METHOD_PUT = 'put';
	/**#@-*/
	
	/**#@+
	 * Encoding type constants
	 */
	const ENCTYPE_URLENCODED = 'application/x-www-form-urlencoded';
	const ENCTYPE_MULTIPART = 'multipart/form-data';
	/**#@-*/
	
	/**
	 * Form metadata and attributes
	 * @var array
	 */
	protected $_attribs = array ();
	
	/**
	 * Decorators for rendering
	 * @var array
	 */
	protected $_decorators = array ();
	
	/**
	 * Default display group class
	 * @var string
	 */
	protected $_defaultDisplayGroupClass = 'Zend_Form_DisplayGroup';
	
	/**
	 * Form description
	 * @var string
	 */
	protected $_description;
	
	/**
	 * Should we disable loading the default decorators?
	 * @var bool
	 */
	protected $_disableLoadDefaultDecorators = false;
	
	/**
	 * Display group prefix paths
	 * @var array
	 */
	protected $_displayGroupPrefixPaths = array ();
	
	/**
	 * Groups of elements grouped for display purposes
	 * @var array
	 */
	protected $_displayGroups = array ();
	
	/**
	 * Global decorators to apply to all elements
	 * @var null|array
	 */
	protected $_elementDecorators;
	
	/**
	 * Prefix paths to use when creating elements
	 * @var array
	 */
	protected $_elementPrefixPaths = array ();
	
	/**
	 * Form elements
	 * @var array
	 */
	protected $_elements = array ();
	
	/**
	 * Array to which elements belong (if any)
	 * @var string
	 */
	protected $_elementsBelongTo;
	
	/**
	 * Custom form-level error messages
	 * @var array
	 */
	protected $_errorMessages = array ();
	
	/**
	 * Are there errors in the form?
	 * @var bool
	 */
	protected $_errorsExist = false;
	
	/**
	 * Has the form been manually flagged as an error?
	 * @var bool
	 */
	protected $_errorsForced = false;
	
	/**
	 * Form order
	 * @var int|null
	 */
	protected $_formOrder;
	
	/**
	 * Whether or not form elements are members of an array
	 * @var bool
	 */
	protected $_isArray = false;
	
	/**
	 * Form legend
	 * @var string
	 */
	protected $_legend;
	
	/**
	 * Plugin loaders
	 * @var array
	 */
	protected $_loaders = array ();
	
	/**
	 * Allowed form methods
	 * @var array
	 */
	protected $_methods = array ('delete', 'get', 'post', 'put' );
	
	/**
	 * Order in which to display and iterate elements
	 * @var array
	 */
	protected $_order = array ();
	
	/**
	 * Whether internal order has been updated or not
	 * @var bool
	 */
	protected $_orderUpdated = false;
	
	/**
	 * Sub form prefix paths
	 * @var array
	 */
	protected $_subFormPrefixPaths = array ();
	
	/**
	 * Sub forms
	 * @var array
	 */
	protected $_subForms = array ();
	
	/**
	 * @var Zend_Translate
	 */
	protected $_translator;
	
	/**
	 * Global default translation adapter
	 * @var Zend_Translate
	 */
	protected static $_translatorDefault;
	
	/**
	 * is the translator disabled?
	 * @var bool
	 */
	protected $_translatorDisabled = false;
	
	/**
	 * @var Zend_View_Interface
	 */
	protected $_view;
	
	/**
	 * @var bool
	 */
	protected $_isRendered = false;
	
	/**
	 * Constructor
	 *
	 * Registers form view helper as decorator
	 *
	 * @param mixed $options
	 * @return void
	 */
	public function __construct($options = null) {
		if (is_array ( $options )) {
			$this->setOptions ( $options );
		} elseif ($options instanceof Zend_Config) {
			$this->setConfig ( $options );
		}
		
		// Extensions...
		$this->init ();
		
		$this->loadDefaultDecorators ();
	}
	
	/**
	 * Clone form object and all children
	 *
	 * @return void
	 */
	public function __clone() {
		$elements = array ();
		foreach ( $this->getElements () as $name => $element ) {
			$elements [] = clone $element;
		}
		$this->setElements ( $elements );
		
		$subForms = array ();
		foreach ( $this->getSubForms () as $name => $subForm ) {
			$subForms [$name] = clone $subForm;
		}
		$this->setSubForms ( $subForms );
		
		$displayGroups = array ();
		foreach ( $this->_displayGroups as $group ) {
			$clone = clone $group;
			$elements = array ();
			foreach ( $clone->getElements () as $name => $e ) {
				$elements [] = $this->getElement ( $name );
			}
			$clone->setElements ( $elements );
			$displayGroups [] = $clone;
		}
		$this->setDisplayGroups ( $displayGroups );
	}
	
	/**
	 * Reset values of form
	 *
	 * @return Zend_Form
	 */
	public function reset() {
		foreach ( $this->getElements () as $element ) {
			$element->setValue ( null );
		}
		foreach ( $this->getSubForms () as $subForm ) {
			$subForm->reset ();
		}
		
		return $this;
	}
	
	/**
	 * Initialize form (used by extending classes)
	 *
	 * @return void
	 */
	public function init() {
	}
	
	/**
	 * Set form state from options array
	 *
	 * @param  array $options
	 * @return Zend_Form
	 */
	public function setOptions(array $options) {
		if (isset ( $options ['prefixPath'] )) {
			$this->addPrefixPaths ( $options ['prefixPath'] );
			unset ( $options ['prefixPath'] );
		}
		
		if (isset ( $options ['elementPrefixPath'] )) {
			$this->addElementPrefixPaths ( $options ['elementPrefixPath'] );
			unset ( $options ['elementPrefixPath'] );
		}
		
		if (isset ( $options ['displayGroupPrefixPath'] )) {
			$this->addDisplayGroupPrefixPaths ( $options ['displayGroupPrefixPath'] );
			unset ( $options ['displayGroupPrefixPath'] );
		}
		
		if (isset ( $options ['elementDecorators'] )) {
			$this->_elementDecorators = $options ['elementDecorators'];
			unset ( $options ['elementDecorators'] );
		}
		
		if (isset ( $options ['elements'] )) {
			$this->setElements ( $options ['elements'] );
			unset ( $options ['elements'] );
		}
		
		if (isset ( $options ['defaultDisplayGroupClass'] )) {
			$this->setDefaultDisplayGroupClass ( $options ['defaultDisplayGroupClass'] );
			unset ( $options ['defaultDisplayGroupClass'] );
		}
		
		if (isset ( $options ['displayGroupDecorators'] )) {
			$displayGroupDecorators = $options ['displayGroupDecorators'];
			unset ( $options ['displayGroupDecorators'] );
		}
		
		if (isset ( $options ['elementsBelongTo'] )) {
			$elementsBelongTo = $options ['elementsBelongTo'];
			unset ( $options ['elementsBelongTo'] );
		}
		
		if (isset ( $options ['attribs'] )) {
			$this->addAttribs ( $options ['attribs'] );
			unset ( $options ['attribs'] );
		}
		
		$forbidden = array ('Options', 'Config', 'PluginLoader', 'SubForms', 'Translator', 'Attrib', 'Default' );
		
		foreach ( $options as $key => $value ) {
			$normalized = ucfirst ( $key );
			if (in_array ( $normalized, $forbidden )) {
				continue;
			}
			
			$method = 'set' . $normalized;
			if (method_exists ( $this, $method )) {
				if ($normalized == 'View' && ! ($value instanceof Zend_View_Interface)) {
					continue;
				}
				$this->$method ( $value );
			} else {
				$this->setAttrib ( $key, $value );
			}
		}
		
		if (isset ( $displayGroupDecorators )) {
			$this->setDisplayGroupDecorators ( $displayGroupDecorators );
		}
		
		if (isset ( $elementsBelongTo )) {
			$this->setElementsBelongTo ( $elementsBelongTo );
		}
		
		return $this;
	}
	
	/**
	 * Set form state from config object
	 *
	 * @param  Zend_Config $config
	 * @return Zend_Form
	 */
	public function setConfig(Zend_Config $config) {
		return $this->setOptions ( $config->toArray () );
	}
	
	// Loaders
	

	/**
	 * Set plugin loaders for use with decorators and elements
	 *
	 * @param  Zend_Loader_PluginLoader_Interface $loader
	 * @param  string $type 'decorator' or 'element'
	 * @return Zend_Form
	 * @throws Zend_Form_Exception on invalid type
	 */
	public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type = null) {
		$type = strtoupper ( $type );
		switch ($type) {
			case self::DECORATOR :
			case self::ELEMENT :
				$this->_loaders [$type] = $loader;
				return $this;
			default :
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception ( sprintf ( 'Invalid type "%s" provided to setPluginLoader()', $type ) );
		}
	}
	
	/**
	 * Retrieve plugin loader for given type
	 *
	 * $type may be one of:
	 * - decorator
	 * - element
	 *
	 * If a plugin loader does not exist for the given type, defaults are
	 * created.
	 *
	 * @param  string $type
	 * @return Zend_Loader_PluginLoader_Interface
	 */
	public function getPluginLoader($type = null) {
		$type = strtoupper ( $type );
		if (! isset ( $this->_loaders [$type] )) {
			switch ($type) {
				case self::DECORATOR :
					$prefixSegment = 'Form_Decorator';
					$pathSegment = 'Form/Decorator';
					break;
				case self::ELEMENT :
					$prefixSegment = 'Form_Element';
					$pathSegment = 'Form/Element';
					break;
				default :
					require_once 'Zend/Form/Exception.php';
					throw new Zend_Form_Exception ( sprintf ( 'Invalid type "%s" provided to getPluginLoader()', $type ) );
			}
			
			require_once 'Zend/Loader/PluginLoader.php';
			$this->_loaders [$type] = new Zend_Loader_PluginLoader ( array ('Zend_' . $prefixSegment . '_' => 'Zend/' . $pathSegment . '/' ) );
		}
		
		return $this->_loaders [$type];
	}
	
	/**
	 * Add prefix path for plugin loader
	 *
	 * If no $type specified, assumes it is a base path for both filters and
	 * validators, and sets each according to the following rules:
	 * - decorators: $prefix = $prefix . '_Decorator'
	 * - elements: $prefix = $prefix . '_Element'
	 *
	 * Otherwise, the path prefix is set on the appropriate plugin loader.
	 *
	 * If $type is 'decorator', sets the path in the decorator plugin loader
	 * for all elements. Additionally, if no $type is provided,
	 * the prefix and path is added to both decorator and element
	 * plugin loader with following settings:
	 * $prefix . '_Decorator', $path . '/Decorator/'
	 * $prefix . '_Element', $path . '/Element/'
	 *
	 * @param  string $prefix
	 * @param  string $path
	 * @param  string $type
	 * @return Zend_Form
	 * @throws Zend_Form_Exception for invalid type
	 */
	public function addPrefixPath($prefix, $path, $type = null) {
		$type = strtoupper ( $type );
		switch ($type) {
			case self::DECORATOR :
			case self::ELEMENT :
				$loader = $this->getPluginLoader ( $type );
				$loader->addPrefixPath ( $prefix, $path );
				return $this;
			case null :
				$prefix = rtrim ( $prefix, '_' );
				$path = rtrim ( $path, DIRECTORY_SEPARATOR );
				foreach ( array (self::DECORATOR, self::ELEMENT ) as $type ) {
					$cType = ucfirst ( strtolower ( $type ) );
					$pluginPath = $path . DIRECTORY_SEPARATOR . $cType . DIRECTORY_SEPARATOR;
					$pluginPrefix = $prefix . '_' . $cType;
					$loader = $this->getPluginLoader ( $type );
					$loader->addPrefixPath ( $pluginPrefix, $pluginPath );
				}
				return $this;
			default :
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception ( sprintf ( 'Invalid type "%s" provided to getPluginLoader()', $type ) );
		}
	}
	
	/**
	 * Add many prefix paths at once
	 *
	 * @param  array $spec
	 * @return Zend_Form
	 */
	public function addPrefixPaths(array $spec) {
		if (isset ( $spec ['prefix'] ) && isset ( $spec ['path'] )) {
			return $this->addPrefixPath ( $spec ['prefix'], $spec ['path'] );
		}
		foreach ( $spec as $type => $paths ) {
			if (is_numeric ( $type ) && is_array ( $paths )) {
				$type = null;
				if (isset ( $paths ['prefix'] ) && isset ( $paths ['path'] )) {
					if (isset ( $paths ['type'] )) {
						$type = $paths ['type'];
					}
					$this->addPrefixPath ( $paths ['prefix'], $paths ['path'], $type );
				}
			} elseif (! is_numeric ( $type )) {
				if (! isset ( $paths ['prefix'] ) || ! isset ( $paths ['path'] )) {
					continue;
				}
				$this->addPrefixPath ( $paths ['prefix'], $paths ['path'], $type );
			}
		}
		return $this;
	}
	
	/**
	 * Add prefix path for all elements
	 *
	 * @param  string $prefix
	 * @param  string $path
	 * @param  string $type
	 * @return Zend_Form
	 */
	public function addElementPrefixPath($prefix, $path, $type = null) {
		$this->_elementPrefixPaths [] = array ('prefix' => $prefix, 'path' => $path, 'type' => $type );
		
		foreach ( $this->getElements () as $element ) {
			$element->addPrefixPath ( $prefix, $path, $type );
		}
		
		foreach ( $this->getSubForms () as $subForm ) {
			$subForm->addElementPrefixPath ( $prefix, $path, $type );
		}
		
		return $this;
	}
	
	/**
	 * Add prefix paths for all elements
	 *
	 * @param  array $spec
	 * @return Zend_Form
	 */
	public function addElementPrefixPaths(array $spec) {
		$this->_elementPrefixPaths = $this->_elementPrefixPaths + $spec;
		
		foreach ( $this->getElements () as $element ) {
			$element->addPrefixPaths ( $spec );
		}
		
		return $this;
	}
	
	/**
	 * Add prefix path for all display groups
	 *
	 * @param  string $prefix
	 * @param  string $path
	 * @return Zend_Form
	 */
	public function addDisplayGroupPrefixPath($prefix, $path) {
		$this->_displayGroupPrefixPaths [] = array ('prefix' => $prefix, 'path' => $path );
		
		foreach ( $this->getDisplayGroups () as $group ) {
			$group->addPrefixPath ( $prefix, $path );
		}
		
		return $this;
	}
	
	/**
	 * Add multiple display group prefix paths at once
	 *
	 * @param  array $spec
	 * @return Zend_Form
	 */
	public function addDisplayGroupPrefixPaths(array $spec) {
		foreach ( $spec as $key => $value ) {
			if (is_string ( $value ) && ! is_numeric ( $key )) {
				$this->addDisplayGroupPrefixPath ( $key, $value );
				continue;
			}
			
			if (is_string ( $value ) && is_numeric ( $key )) {
				continue;
			}
			
			if (is_array ( $value )) {
				$count = count ( $value );
				if (array_keys ( $value ) === range ( 0, $count - 1 )) {
					if ($count < 2) {
						continue;
					}
					$prefix = array_shift ( $value );
					$path = array_shift ( $value );
					$this->addDisplayGroupPrefixPath ( $prefix, $path );
					continue;
				}
				if (array_key_exists ( 'prefix', $value ) && array_key_exists ( 'path', $value )) {
					$this->addDisplayGroupPrefixPath ( $value ['prefix'], $value ['path'] );
				}
			}
		}
		return $this;
	}
	
	// Form metadata:
	

	/**
	 * Set form attribute
	 *
	 * @param  string $key
	 * @param  mixed $value
	 * @return Zend_Form
	 */
	public function setAttrib($key, $value) {
		$key = ( string ) $key;
		$this->_attribs [$key] = $value;
		return $this;
	}
	
	/**
	 * Add multiple form attributes at once
	 *
	 * @param  array $attribs
	 * @return Zend_Form
	 */
	public function addAttribs(array $attribs) {
		foreach ( $attribs as $key => $value ) {
			$this->setAttrib ( $key, $value );
		}
		return $this;
	}
	
	/**
	 * Set multiple form attributes at once
	 *
	 * Overwrites any previously set attributes.
	 *
	 * @param  array $attribs
	 * @return Zend_Form
	 */
	public function setAttribs(array $attribs) {
		$this->clearAttribs ();
		return $this->addAttribs ( $attribs );
	}
	
	/**
	 * Retrieve a single form attribute
	 *
	 * @param  string $key
	 * @return mixed
	 */
	public function getAttrib($key) {
		$key = ( string ) $key;
		if (! isset ( $this->_attribs [$key] )) {
			return null;
		}
		
		return $this->_attribs [$key];
	}
	
	/**
	 * Retrieve all form attributes/metadata
	 *
	 * @return array
	 */
	public function getAttribs() {
		return $this->_attribs;
	}
	
	/**
	 * Remove attribute
	 *
	 * @param  string $key
	 * @return bool
	 */
	public function removeAttrib($key) {
		if (isset ( $this->_attribs [$key] )) {
			unset ( $this->_attribs [$key] );
			return true;
		}
		
		return false;
	}
	
	/**
	 * Clear all form attributes
	 *
	 * @return Zend_Form
	 */
	public function clearAttribs() {
		$this->_attribs = array ();
		return $this;
	}
	
	/**
	 * Set form action
	 *
	 * @param  string $action
	 * @return Zend_Form
	 */
	public function setAction($action) {
		return $this->setAttrib ( 'action', ( string ) $action );
	}
	
	/**
	 * Get form action
	 *
	 * Sets default to '' if not set.
	 *
	 * @return string
	 */
	public function getAction() {
		$action = $this->getAttrib ( 'action' );
		if (null === $action) {
			$action = '';
			$this->setAction ( $action );
		}
		return $action;
	}
	
	/**
	 * Set form method
	 *
	 * Only values in {@link $_methods()} allowed
	 *
	 * @param  string $method
	 * @return Zend_Form
	 * @throws Zend_Form_Exception
	 */
	public function setMethod($method) {
		$method = strtolower ( $method );
		if (! in_array ( $method, $this->_methods )) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( sprintf ( '"%s" is an invalid form method', $method ) );
		}
		$this->setAttrib ( 'method', $method );
		return $this;
	}
	
	/**
	 * Retrieve form method
	 *
	 * @return string
	 */
	public function getMethod() {
		if (null === ($method = $this->getAttrib ( 'method' ))) {
			$method = self::METHOD_POST;
			$this->setAttrib ( 'method', $method );
		}
		return strtolower ( $method );
	}
	
	/**
	 * Set encoding type
	 *
	 * @param  string $value
	 * @return Zend_Form
	 */
	public function setEnctype($value) {
		$this->setAttrib ( 'enctype', $value );
		return $this;
	}
	
	/**
	 * Get encoding type
	 *
	 * @return string
	 */
	public function getEnctype() {
		if (null === ($enctype = $this->getAttrib ( 'enctype' ))) {
			$enctype = self::ENCTYPE_URLENCODED;
			$this->setAttrib ( 'enctype', $enctype );
		}
		return $this->getAttrib ( 'enctype' );
	}
	
	/**
	 * Filter a name to only allow valid variable characters
	 *
	 * @param  string $value
	 * @param  bool $allowBrackets
	 * @return string
	 */
	public function filterName($value, $allowBrackets = false) {
		$charset = '^a-zA-Z0-9_\x7f-\xff';
		if ($allowBrackets) {
			$charset .= '\[\]';
		}
		return preg_replace ( '/[' . $charset . ']/', '', ( string ) $value );
	}
	
	/**
	 * Set form name
	 *
	 * @param  string $name
	 * @return Zend_Form
	 */
	public function setName($name) {
		$name = $this->filterName ( $name );
		if ('' === ( string ) $name) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( 'Invalid name provided; must contain only valid variable characters and be non-empty' );
		}
		
		return $this->setAttrib ( 'name', $name );
	}
	
	/**
	 * Get name attribute
	 *
	 * @return null|string
	 */
	public function getName() {
		return $this->getAttrib ( 'name' );
	}
	
	/**
	 * Get fully qualified name
	 *
	 * Places name as subitem of array and/or appends brackets.
	 *
	 * @return string
	 */
	public function getFullyQualifiedName() {
		return $this->getName ();
	}
	
	/**
	 * Get element id
	 *
	 * @return string
	 */
	public function getId() {
		if (null !== ($id = $this->getAttrib ( 'id' ))) {
			return $id;
		}
		
		$id = $this->getFullyQualifiedName ();
		
		// Bail early if no array notation detected
		if (! strstr ( $id, '[' )) {
			return $id;
		}
		
		// Strip array notation
		if ('[]' == substr ( $id, - 2 )) {
			$id = substr ( $id, 0, strlen ( $id ) - 2 );
		}
		$id = str_replace ( '][', '-', $id );
		$id = str_replace ( array (']', '[' ), '-', $id );
		$id = trim ( $id, '-' );
		
		return $id;
	}
	
	/**
	 * Set form legend
	 *
	 * @param  string $value
	 * @return Zend_Form
	 */
	public function setLegend($value) {
		$this->_legend = ( string ) $value;
		return $this;
	}
	
	/**
	 * Get form legend
	 *
	 * @return string
	 */
	public function getLegend() {
		return $this->_legend;
	}
	
	/**
	 * Set form description
	 *
	 * @param  string $value
	 * @return Zend_Form
	 */
	public function setDescription($value) {
		$this->_description = ( string ) $value;
		return $this;
	}
	
	/**
	 * Retrieve form description
	 *
	 * @return string
	 */
	public function getDescription() {
		return $this->_description;
	}
	
	/**
	 * Set form order
	 *
	 * @param  int $index
	 * @return Zend_Form
	 */
	public function setOrder($index) {
		$this->_formOrder = ( int ) $index;
		return $this;
	}
	
	/**
	 * Get form order
	 *
	 * @return int|null
	 */
	public function getOrder() {
		return $this->_formOrder;
	}
	
	/**
	 * When calling renderFormElements or render this method
	 * is used to set $_isRendered member to prevent repeatedly
	 * merging belongsTo setting
	 */
	protected function _setIsRendered() {
		$this->_isRendered = true;
		return $this;
	}
	
	/**
	 * Get the value of $_isRendered member
	 */
	protected function _getIsRendered() {
		return ( bool ) $this->_isRendered;
	}
	
	// Element interaction:
	

	/**
	 * Add a new element
	 *
	 * $element may be either a string element type, or an object of type
	 * Zend_Form_Element. If a string element type is provided, $name must be
	 * provided, and $options may be optionally provided for configuring the
	 * element.
	 *
	 * If a Zend_Form_Element is provided, $name may be optionally provided,
	 * and any provided $options will be ignored.
	 *
	 * @param  string|Zend_Form_Element $element
	 * @param  string $name
	 * @param  array|Zend_Config $options
	 * @throws Zend_Form_Exception on invalid element
	 * @return Zend_Form
	 */
	public function addElement($element, $name = null, $options = null) {
		if (is_string ( $element )) {
			if (null === $name) {
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception ( 'Elements specified by string must have an accompanying name' );
			}
			
			if (is_array ( $this->_elementDecorators )) {
				if (null === $options) {
					$options = array ('decorators' => $this->_elementDecorators );
				} elseif ($options instanceof Zend_Config) {
					$options = $options->toArray ();
				}
				if (is_array ( $options ) && ! array_key_exists ( 'decorators', $options )) {
					$options ['decorators'] = $this->_elementDecorators;
				}
			}
			
			$this->_elements [$name] = $this->createElement ( $element, $name, $options );
		} elseif ($element instanceof Zend_Form_Element) {
			$prefixPaths = array ();
			$prefixPaths ['decorator'] = $this->getPluginLoader ( 'decorator' )->getPaths ();
			if (! empty ( $this->_elementPrefixPaths )) {
				$prefixPaths = array_merge ( $prefixPaths, $this->_elementPrefixPaths );
			}
			
			if (null === $name) {
				$name = $element->getName ();
			}
			
			$this->_elements [$name] = $element;
			$this->_elements [$name]->addPrefixPaths ( $prefixPaths );
		} else {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( 'Element must be specified by string or Zend_Form_Element instance' );
		}
		
		$this->_order [$name] = $this->_elements [$name]->getOrder ();
		$this->_orderUpdated = true;
		$this->_setElementsBelongTo ( $name );
		
		return $this;
	}
	
	/**
	 * Create an element
	 *
	 * Acts as a factory for creating elements. Elements created with this
	 * method will not be attached to the form, but will contain element
	 * settings as specified in the form object (including plugin loader
	 * prefix paths, default decorators, etc.).
	 *
	 * @param  string $type
	 * @param  string $name
	 * @param  array|Zend_Config $options
	 * @return Zend_Form_Element
	 */
	public function createElement($type, $name, $options = null) {
		if (! is_string ( $type )) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( 'Element type must be a string indicating type' );
		}
		
		if (! is_string ( $name )) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( 'Element name must be a string' );
		}
		
		$prefixPaths = array ();
		$prefixPaths ['decorator'] = $this->getPluginLoader ( 'decorator' )->getPaths ();
		if (! empty ( $this->_elementPrefixPaths )) {
			$prefixPaths = array_merge ( $prefixPaths, $this->_elementPrefixPaths );
		}
		
		if ($options instanceof Zend_Config) {
			$options = $options->toArray ();
		}
		
		if ((null === $options) || ! is_array ( $options )) {
			$options = array ('prefixPath' => $prefixPaths );
		} elseif (is_array ( $options )) {
			if (array_key_exists ( 'prefixPath', $options )) {
				$options ['prefixPath'] = array_merge ( $prefixPaths, $options ['prefixPath'] );
			} else {
				$options ['prefixPath'] = $prefixPaths;
			}
		}
		
		$class = $this->getPluginLoader ( self::ELEMENT )->load ( $type );
		$element = new $class ( $name, $options );
		
		return $element;
	}
	
	/**
	 * Add multiple elements at once
	 *
	 * @param  array $elements
	 * @return Zend_Form
	 */
	public function addElements(array $elements) {
		foreach ( $elements as $key => $spec ) {
			$name = null;
			if (! is_numeric ( $key )) {
				$name = $key;
			}
			
			if (is_string ( $spec ) || ($spec instanceof Zend_Form_Element)) {
				$this->addElement ( $spec, $name );
				continue;
			}
			
			if (is_array ( $spec )) {
				$argc = count ( $spec );
				$options = array ();
				if (isset ( $spec ['type'] )) {
					$type = $spec ['type'];
					if (isset ( $spec ['name'] )) {
						$name = $spec ['name'];
					}
					if (isset ( $spec ['options'] )) {
						$options = $spec ['options'];
					}
					$this->addElement ( $type, $name, $options );
				} else {
					switch ($argc) {
						case 0 :
							continue;
						case (1 <= $argc) :
							$type = array_shift ( $spec );
						case (2 <= $argc) :
							if (null === $name) {
								$name = array_shift ( $spec );
							} else {
								$options = array_shift ( $spec );
							}
						case (3 <= $argc) :
							if (empty ( $options )) {
								$options = array_shift ( $spec );
							}
						default :
							$this->addElement ( $type, $name, $options );
					}
				}
			}
		}
		return $this;
	}
	
	/**
	 * Set form elements (overwrites existing elements)
	 *
	 * @param  array $elements
	 * @return Zend_Form
	 */
	public function setElements(array $elements) {
		$this->clearElements ();
		return $this->addElements ( $elements );
	}
	
	/**
	 * Retrieve a single element
	 *
	 * @param  string $name
	 * @return Zend_Form_Element|null
	 */
	public function getElement($name) {
		if (array_key_exists ( $name, $this->_elements )) {
			return $this->_elements [$name];
		}
		return null;
	}
	
	/**
	 * Retrieve all elements
	 *
	 * @return array
	 */
	public function getElements() {
		return $this->_elements;
	}
	
	/**
	 * Remove element
	 *
	 * @param  string $name
	 * @return boolean
	 */
	public function removeElement($name) {
		$name = ( string ) $name;
		if (isset ( $this->_elements [$name] )) {
			unset ( $this->_elements [$name] );
			if (array_key_exists ( $name, $this->_order )) {
				unset ( $this->_order [$name] );
				$this->_orderUpdated = true;
			} else {
				foreach ( $this->_displayGroups as $group ) {
					if (null !== $group->getElement ( $name )) {
						$group->removeElement ( $name );
					}
				}
			}
			return true;
		}
		
		return false;
	}
	
	/**
	 * Remove all form elements
	 *
	 * @return Zend_Form
	 */
	public function clearElements() {
		foreach ( array_keys ( $this->_elements ) as $key ) {
			if (array_key_exists ( $key, $this->_order )) {
				unset ( $this->_order [$key] );
			}
		}
		$this->_elements = array ();
		$this->_orderUpdated = true;
		return $this;
	}
	
	/**
	 * Set default values for elements
	 *
	 * Sets values for all elements specified in the array of $defaults.
	 *
	 * @param  array $defaults
	 * @return Zend_Form
	 */
	public function setDefaults(array $defaults) {
		$eBelongTo = null;
		
		if ($this->isArray ()) {
			$eBelongTo = $this->getElementsBelongTo ();
			$defaults = $this->_dissolveArrayValue ( $defaults, $eBelongTo );
		}
		foreach ( $this->getElements () as $name => $element ) {
			$check = $defaults;
			if (($belongsTo = $element->getBelongsTo ()) !== $eBelongTo) {
				$check = $this->_dissolveArrayValue ( $defaults, $belongsTo );
			}
			if (array_key_exists ( $name, $check )) {
				$this->setDefault ( $name, $check [$name] );
				$defaults = $this->_dissolveArrayUnsetKey ( $defaults, $belongsTo, $name );
			}
		}
		foreach ( $this->getSubForms () as $name => $form ) {
			if (! $form->isArray () && array_key_exists ( $name, $defaults )) {
				$form->setDefaults ( $defaults [$name] );
			} else {
				$form->setDefaults ( $defaults );
			}
		}
		return $this;
	}
	
	/**
	 * Set default value for an element
	 *
	 * @param  string $name
	 * @param  mixed $value
	 * @return Zend_Form
	 */
	public function setDefault($name, $value) {
		$name = ( string ) $name;
		if ($element = $this->getElement ( $name )) {
			$element->setValue ( $value );
		} else {
			if (is_scalar ( $value )) {
				foreach ( $this->getSubForms () as $subForm ) {
					$subForm->setDefault ( $name, $value );
				}
			} elseif (is_array ( $value ) && ($subForm = $this->getSubForm ( $name ))) {
				$subForm->setDefaults ( $value );
			}
		}
		return $this;
	}
	
	/**
	 * Retrieve value for single element
	 *
	 * @param  string $name
	 * @return mixed
	 */
	public function getValue($name) {
		if ($element = $this->getElement ( $name )) {
			return $element->getValue ();
		}
		
		if ($subForm = $this->getSubForm ( $name )) {
			return $subForm->getValues ( true );
		}
		
		foreach ( $this->getSubForms () as $subForm ) {
			if ($name == $subForm->getElementsBelongTo ()) {
				return $subForm->getValues ( true );
			}
		}
		return null;
	}
	
	/**
	 * Retrieve all form element values
	 *
	 * @param  bool $suppressArrayNotation
	 * @return array
	 */
	public function getValues($suppressArrayNotation = false) {
		$values = array ();
		$eBelongTo = null;
		
		if ($this->isArray ()) {
			$eBelongTo = $this->getElementsBelongTo ();
		}
		
		foreach ( $this->getElements () as $key => $element ) {
			if (! $element->getIgnore ()) {
				$merge = array ();
				if (($belongsTo = $element->getBelongsTo ()) !== $eBelongTo) {
					if ('' !== ( string ) $belongsTo) {
						$key = $belongsTo . '[' . $key . ']';
					}
				}
				$merge = $this->_attachToArray ( $element->getValue (), $key );
				$values = $this->_array_replace_recursive ( $values, $merge );
			}
		}
		foreach ( $this->getSubForms () as $key => $subForm ) {
			$merge = array ();
			if (! $subForm->isArray ()) {
				$merge [$key] = $subForm->getValues ();
			} else {
				$merge = $this->_attachToArray ( $subForm->getValues ( true ), $subForm->getElementsBelongTo () );
			}
			$values = $this->_array_replace_recursive ( $values, $merge );
		}
		
		if (! $suppressArrayNotation && $this->isArray () && ! $this->_getIsRendered ()) {
			$values = $this->_attachToArray ( $values, $this->getElementsBelongTo () );
		}
		
		return $values;
	}
	
	/**
	 * Returns only the valid values from the given form input.
	 *
	 * For models that can be saved in a partially valid state, for example when following the builder,
	 * prototype or state patterns it is particularly interessting to retrieve all the current valid
	 * values to persist them.
	 *
	 * @param  array $data
	 * @param  bool $suppressArrayNotation
	 * @return array
	 */
	public function getValidValues($data, $suppressArrayNotation = false) {
		$values = array ();
		$eBelongTo = null;
		
		if ($this->isArray ()) {
			$eBelongTo = $this->getElementsBelongTo ();
			$data = $this->_dissolveArrayValue ( $data, $eBelongTo );
		}
		$context = $data;
		foreach ( $this->getElements () as $key => $element ) {
			if (! $element->getIgnore ()) {
				$check = $data;
				if (($belongsTo = $element->getBelongsTo ()) !== $eBelongTo) {
					$check = $this->_dissolveArrayValue ( $data, $belongsTo );
				}
				if (isset ( $check [$key] )) {
					if ($element->isValid ( $check [$key], $context )) {
						$merge = array ();
						if ($belongsTo !== $eBelongTo && '' !== ( string ) $belongsTo) {
							$key = $belongsTo . '[' . $key . ']';
						}
						$merge = $this->_attachToArray ( $element->getValue (), $key );
						$values = $this->_array_replace_recursive ( $values, $merge );
					}
					$data = $this->_dissolveArrayUnsetKey ( $data, $belongsTo, $key );
				}
			}
		}
		foreach ( $this->getSubForms () as $key => $form ) {
			$merge = array ();
			if (isset ( $data [$key] ) && ! $form->isArray ()) {
				$tmp = $form->getValidValues ( $data [$key] );
				if (! empty ( $tmp )) {
					$merge [$key] = $tmp;
				}
			} else {
				$tmp = $form->getValidValues ( $data, true );
				if (! empty ( $tmp )) {
					$merge = $this->_attachToArray ( $tmp, $form->getElementsBelongTo () );
				}
			}
			$values = $this->_array_replace_recursive ( $values, $merge );
		}
		if (! $suppressArrayNotation && $this->isArray () && ! empty ( $values ) && ! $this->_getIsRendered ()) {
			$values = $this->_attachToArray ( $values, $this->getElementsBelongTo () );
		}
		
		return $values;
	}
	
	/**
	 * Get unfiltered element value
	 *
	 * @param  string $name
	 * @return mixed
	 */
	public function getUnfilteredValue($name) {
		if ($element = $this->getElement ( $name )) {
			return $element->getUnfilteredValue ();
		}
		return null;
	}
	
	/**
	 * Retrieve all unfiltered element values
	 *
	 * @return array
	 */
	public function getUnfilteredValues() {
		$values = array ();
		foreach ( $this->getElements () as $key => $element ) {
			$values [$key] = $element->getUnfilteredValue ();
		}
		
		return $values;
	}
	
	/**
	 * Set all elements' filters
	 *
	 * @param  array $filters
	 * @return Zend_Form
	 */
	public function setElementFilters(array $filters) {
		foreach ( $this->getElements () as $element ) {
			$element->setFilters ( $filters );
		}
		return $this;
	}
	
	/**
	 * Set name of array elements belong to
	 *
	 * @param  string $array
	 * @return Zend_Form
	 */
	public function setElementsBelongTo($array) {
		$origName = $this->getElementsBelongTo ();
		$name = $this->filterName ( $array, true );
		if ('' === $name) {
			$name = null;
		}
		$this->_elementsBelongTo = $name;
		
		if (null === $name) {
			$this->setIsArray ( false );
			if (null !== $origName) {
				$this->_setElementsBelongTo ();
			}
		} else {
			$this->setIsArray ( true );
			$this->_setElementsBelongTo ();
		}
		
		return $this;
	}
	
	/**
	 * Set array to which elements belong
	 *
	 * @param  string $name Element name
	 * @return void
	 */
	protected function _setElementsBelongTo($name = null) {
		$array = $this->getElementsBelongTo ();
		
		if (null === $array) {
			return;
		}
		
		if (null === $name) {
			foreach ( $this->getElements () as $element ) {
				$element->setBelongsTo ( $array );
			}
		} else {
			if (null !== ($element = $this->getElement ( $name ))) {
				$element->setBelongsTo ( $array );
			}
		}
	}
	
	/**
	 * Get name of array elements belong to
	 *
	 * @return string|null
	 */
	public function getElementsBelongTo() {
		if ((null === $this->_elementsBelongTo) && $this->isArray ()) {
			$name = $this->getName ();
			if ('' !== ( string ) $name) {
				return $name;
			}
		}
		return $this->_elementsBelongTo;
	}
	
	/**
	 * Set flag indicating elements belong to array
	 *
	 * @param  bool $flag Value of flag
	 * @return Zend_Form
	 */
	public function setIsArray($flag) {
		$this->_isArray = ( bool ) $flag;
		return $this;
	}
	
	/**
	 * Get flag indicating if elements belong to an array
	 *
	 * @return bool
	 */
	public function isArray() {
		return $this->_isArray;
	}
	
	// Element groups:
	

	/**
	 * Add a form group/subform
	 *
	 * @param  Zend_Form $form
	 * @param  string $name
	 * @param  int $order
	 * @return Zend_Form
	 */
	public function addSubForm(Zend_Form $form, $name, $order = null) {
		$name = ( string ) $name;
		foreach ( $this->_loaders as $type => $loader ) {
			$loaderPaths = $loader->getPaths ();
			foreach ( $loaderPaths as $prefix => $paths ) {
				foreach ( $paths as $path ) {
					$form->addPrefixPath ( $prefix, $path, $type );
				}
			}
		}
		
		if (! empty ( $this->_elementPrefixPaths )) {
			foreach ( $this->_elementPrefixPaths as $spec ) {
				list ( $prefix, $path, $type ) = array_values ( $spec );
				$form->addElementPrefixPath ( $prefix, $path, $type );
			}
		}
		
		if (! empty ( $this->_displayGroupPrefixPaths )) {
			foreach ( $this->_displayGroupPrefixPaths as $spec ) {
				list ( $prefix, $path ) = array_values ( $spec );
				$form->addDisplayGroupPrefixPath ( $prefix, $path );
			}
		}
		
		if (null !== $order) {
			$form->setOrder ( $order );
		}
		
		if (($oldName = $form->getName ()) && $oldName !== $name && $oldName === $form->getElementsBelongTo ()) {
			$form->setElementsBelongTo ( $name );
		}
		
		$form->setName ( $name );
		$this->_subForms [$name] = $form;
		$this->_order [$name] = $order;
		$this->_orderUpdated = true;
		return $this;
	}
	
	/**
	 * Add multiple form subForms/subforms at once
	 *
	 * @param  array $subForms
	 * @return Zend_Form
	 */
	public function addSubForms(array $subForms) {
		foreach ( $subForms as $key => $spec ) {
			$name = ( string ) $key;
			if ($spec instanceof Zend_Form) {
				$this->addSubForm ( $spec, $name );
				continue;
			}
			
			if (is_array ( $spec )) {
				$argc = count ( $spec );
				$order = null;
				switch ($argc) {
					case 0 :
						continue;
					case (1 <= $argc) :
						$subForm = array_shift ( $spec );
					case (2 <= $argc) :
						$name = array_shift ( $spec );
					case (3 <= $argc) :
						$order = array_shift ( $spec );
					default :
						$this->addSubForm ( $subForm, $name, $order );
				}
			}
		}
		return $this;
	}
	
	/**
	 * Set multiple form subForms/subforms (overwrites)
	 *
	 * @param  array $subForms
	 * @return Zend_Form
	 */
	public function setSubForms(array $subForms) {
		$this->clearSubForms ();
		return $this->addSubForms ( $subForms );
	}
	
	/**
	 * Retrieve a form subForm/subform
	 *
	 * @param  string $name
	 * @return Zend_Form|null
	 */
	public function getSubForm($name) {
		$name = ( string ) $name;
		if (isset ( $this->_subForms [$name] )) {
			return $this->_subForms [$name];
		}
		return null;
	}
	
	/**
	 * Retrieve all form subForms/subforms
	 *
	 * @return array
	 */
	public function getSubForms() {
		return $this->_subForms;
	}
	
	/**
	 * Remove form subForm/subform
	 *
	 * @param  string $name
	 * @return boolean
	 */
	public function removeSubForm($name) {
		$name = ( string ) $name;
		if (array_key_exists ( $name, $this->_subForms )) {
			unset ( $this->_subForms [$name] );
			if (array_key_exists ( $name, $this->_order )) {
				unset ( $this->_order [$name] );
				$this->_orderUpdated = true;
			}
			return true;
		}
		
		return false;
	}
	
	/**
	 * Remove all form subForms/subforms
	 *
	 * @return Zend_Form
	 */
	public function clearSubForms() {
		foreach ( array_keys ( $this->_subForms ) as $key ) {
			if (array_key_exists ( $key, $this->_order )) {
				unset ( $this->_order [$key] );
			}
		}
		$this->_subForms = array ();
		$this->_orderUpdated = true;
		return $this;
	}
	
	// Display groups:
	

	/**
	 * Set default display group class
	 *
	 * @param  string $class
	 * @return Zend_Form
	 */
	public function setDefaultDisplayGroupClass($class) {
		$this->_defaultDisplayGroupClass = ( string ) $class;
		return $this;
	}
	
	/**
	 * Retrieve default display group class
	 *
	 * @return string
	 */
	public function getDefaultDisplayGroupClass() {
		return $this->_defaultDisplayGroupClass;
	}
	
	/**
	 * Add a display group
	 *
	 * Groups named elements for display purposes.
	 *
	 * If a referenced element does not yet exist in the form, it is omitted.
	 *
	 * @param  array $elements
	 * @param  string $name
	 * @param  array|Zend_Config $options
	 * @return Zend_Form
	 * @throws Zend_Form_Exception if no valid elements provided
	 */
	public function addDisplayGroup(array $elements, $name, $options = null) {
		$group = array ();
		foreach ( $elements as $element ) {
			if ($element instanceof Zend_Form_Element) {
				$elementName = $element->getName ();
				if (! isset ( $this->_elements [$elementName] )) {
					$this->addElement ( $element );
				}
				$element = $elementName;
			}
			
			if (isset ( $this->_elements [$element] )) {
				$add = $this->getElement ( $element );
				if (null !== $add) {
					$group [] = $add;
				}
			}
		}
		if (empty ( $group )) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( 'No valid elements specified for display group' );
		}
		
		$name = ( string ) $name;
		
		if (is_array ( $options )) {
			$options ['form'] = $this;
			$options ['elements'] = $group;
		} elseif ($options instanceof Zend_Config) {
			$options = $options->toArray ();
			$options ['form'] = $this;
			$options ['elements'] = $group;
		} else {
			$options = array ('form' => $this, 'elements' => $group );
		}
		
		if (isset ( $options ['displayGroupClass'] )) {
			$class = $options ['displayGroupClass'];
			unset ( $options ['displayGroupClass'] );
		} else {
			$class = $this->getDefaultDisplayGroupClass ();
		}
		
		if (! class_exists ( $class )) {
			require_once 'Zend/Loader.php';
			Zend_Loader::loadClass ( $class );
		}
		$this->_displayGroups [$name] = new $class ( $name, $this->getPluginLoader ( self::DECORATOR ), $options );
		
		if (! empty ( $this->_displayGroupPrefixPaths )) {
			$this->_displayGroups [$name]->addPrefixPaths ( $this->_displayGroupPrefixPaths );
		}
		
		$this->_order [$name] = $this->_displayGroups [$name]->getOrder ();
		$this->_orderUpdated = true;
		return $this;
	}
	
	/**
	 * Add a display group object (used with cloning)
	 *
	 * @param  Zend_Form_DisplayGroup $group
	 * @param  string|null $name
	 * @return Zend_Form
	 */
	protected function _addDisplayGroupObject(Zend_Form_DisplayGroup $group, $name = null) {
		if (null === $name) {
			$name = $group->getName ();
			if ('' === ( string ) $name) {
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception ( 'Invalid display group added; requires name' );
			}
		}
		
		$this->_displayGroups [$name] = $group;
		$group->setForm ( $this );
		
		if (! empty ( $this->_displayGroupPrefixPaths )) {
			$this->_displayGroups [$name]->addPrefixPaths ( $this->_displayGroupPrefixPaths );
		}
		
		$this->_order [$name] = $this->_displayGroups [$name]->getOrder ();
		$this->_orderUpdated = true;
		return $this;
	}
	
	/**
	 * Add multiple display groups at once
	 *
	 * @param  array $groups
	 * @return Zend_Form
	 */
	public function addDisplayGroups(array $groups) {
		foreach ( $groups as $key => $spec ) {
			$name = null;
			if (! is_numeric ( $key )) {
				$name = $key;
			}
			
			if ($spec instanceof Zend_Form_DisplayGroup) {
				$this->_addDisplayGroupObject ( $spec );
			}
			
			if (! is_array ( $spec ) || empty ( $spec )) {
				continue;
			}
			
			$argc = count ( $spec );
			$options = array ();
			
			if (isset ( $spec ['elements'] )) {
				$elements = $spec ['elements'];
				if (isset ( $spec ['name'] )) {
					$name = $spec ['name'];
				}
				if (isset ( $spec ['options'] )) {
					$options = $spec ['options'];
				}
				$this->addDisplayGroup ( $elements, $name, $options );
			} else {
				switch ($argc) {
					case (1 <= $argc) :
						$elements = array_shift ( $spec );
						if (! is_array ( $elements ) && (null !== $name)) {
							$elements = array_merge ( ( array ) $elements, $spec );
							$this->addDisplayGroup ( $elements, $name );
							break;
						}
					case (2 <= $argc) :
						if (null !== $name) {
							$options = array_shift ( $spec );
							$this->addDisplayGroup ( $elements, $name, $options );
							break;
						}
						$name = array_shift ( $spec );
					case (3 <= $argc) :
						$options = array_shift ( $spec );
					default :
						$this->addDisplayGroup ( $elements, $name, $options );
				}
			}
		}
		return $this;
	}
	
	/**
	 * Add multiple display groups (overwrites)
	 *
	 * @param  array $groups
	 * @return Zend_Form
	 */
	public function setDisplayGroups(array $groups) {
		return $this->clearDisplayGroups ()->addDisplayGroups ( $groups );
	}
	
	/**
	 * Return a display group
	 *
	 * @param  string $name
	 * @return Zend_Form_DisplayGroup|null
	 */
	public function getDisplayGroup($name) {
		$name = ( string ) $name;
		if (isset ( $this->_displayGroups [$name] )) {
			return $this->_displayGroups [$name];
		}
		
		return null;
	}
	
	/**
	 * Return all display groups
	 *
	 * @return array
	 */
	public function getDisplayGroups() {
		return $this->_displayGroups;
	}
	
	/**
	 * Remove a display group by name
	 *
	 * @param  string $name
	 * @return boolean
	 */
	public function removeDisplayGroup($name) {
		$name = ( string ) $name;
		if (array_key_exists ( $name, $this->_displayGroups )) {
			foreach ( $this->_displayGroups [$name] as $key => $element ) {
				if (array_key_exists ( $key, $this->_elements )) {
					$this->_order [$key] = $element->getOrder ();
					$this->_orderUpdated = true;
				}
			}
			unset ( $this->_displayGroups [$name] );
			
			if (array_key_exists ( $name, $this->_order )) {
				unset ( $this->_order [$name] );
				$this->_orderUpdated = true;
			}
			return true;
		}
		
		return false;
	}
	
	/**
	 * Remove all display groups
	 *
	 * @return Zend_Form
	 */
	public function clearDisplayGroups() {
		foreach ( $this->_displayGroups as $key => $group ) {
			if (array_key_exists ( $key, $this->_order )) {
				unset ( $this->_order [$key] );
			}
			foreach ( $group as $name => $element ) {
				if (isset ( $this->_elements [$name] )) {
					$this->_order [$name] = $element->getOrder ();
				}
				$this->_order [$name] = $element->getOrder ();
			}
		}
		$this->_displayGroups = array ();
		$this->_orderUpdated = true;
		return $this;
	}
	
	// Processing
	

	/**
	 * Populate form
	 *
	 * Proxies to {@link setDefaults()}
	 *
	 * @param  array $values
	 * @return Zend_Form
	 */
	public function populate(array $values) {
		return $this->setDefaults ( $values );
	}
	
	/**
	 * Determine array key name from given value
	 *
	 * Given a value such as foo[bar][baz], returns the last element (in this case, 'baz').
	 *
	 * @param  string $value
	 * @return string
	 */
	protected function _getArrayName($value) {
		if (! is_string ( $value ) || '' === $value) {
			return $value;
		}
		
		if (! strstr ( $value, '[' )) {
			return $value;
		}
		
		$endPos = strlen ( $value ) - 1;
		if (']' != $value [$endPos]) {
			return $value;
		}
		
		$start = strrpos ( $value, '[' ) + 1;
		$name = substr ( $value, $start, $endPos - $start );
		return $name;
	}
	
	/**
	 * Extract the value by walking the array using given array path.
	 *
	 * Given an array path such as foo[bar][baz], returns the value of the last
	 * element (in this case, 'baz').
	 *
	 * @param  array $value Array to walk
	 * @param  string $arrayPath Array notation path of the part to extract
	 * @return string
	 */
	protected function _dissolveArrayValue($value, $arrayPath) {
		// As long as we have more levels
		while ( $arrayPos = strpos ( $arrayPath, '[' ) ) {
			// Get the next key in the path
			$arrayKey = trim ( substr ( $arrayPath, 0, $arrayPos ), ']' );
			
			// Set the potentially final value or the next search point in the array
			if (isset ( $value [$arrayKey] )) {
				$value = $value [$arrayKey];
			}
			
			// Set the next search point in the path
			$arrayPath = trim ( substr ( $arrayPath, $arrayPos + 1 ), ']' );
		}
		
		if (isset ( $value [$arrayPath] )) {
			$value = $value [$arrayPath];
		}
		
		return $value;
	}
	
	/**
	 * Given an array, an optional arrayPath and a key this method
	 * dissolves the arrayPath and unsets the key within the array
	 * if it exists.
	 *
	 * @param array $array
	 * @param string|null $arrayPath
	 * @param string $key
	 * @return array
	 */
	protected function _dissolveArrayUnsetKey($array, $arrayPath, $key) {
		$unset = & $array;
		$path = trim ( strtr ( ( string ) $arrayPath, array ('[' => '/', ']' => '' ) ), '/' );
		$segs = ('' !== $path) ? explode ( '/', $path ) : array ();
		
		foreach ( $segs as $seg ) {
			if (! array_key_exists ( $seg, ( array ) $unset )) {
				return $array;
			}
			$unset = & $unset [$seg];
		}
		if (array_key_exists ( $key, ( array ) $unset )) {
			unset ( $unset [$key] );
		}
		return $array;
	}
	
	/**
	 * Converts given arrayPath to an array and attaches given value at the end of it.
	 *
	 * @param  mixed $value The value to attach
	 * @param  string $arrayPath Given array path to convert and attach to.
	 * @return array
	 */
	protected function _attachToArray($value, $arrayPath) {
		// As long as we have more levels
		while ( $arrayPos = strrpos ( $arrayPath, '[' ) ) {
			// Get the next key in the path
			$arrayKey = trim ( substr ( $arrayPath, $arrayPos + 1 ), ']' );
			
			// Attach
			$value = array ($arrayKey => $value );
			
			// Set the next search point in the path
			$arrayPath = trim ( substr ( $arrayPath, 0, $arrayPos ), ']' );
		}
		
		$value = array ($arrayPath => $value );
		
		return $value;
	}
	
	/**
	 * Returns a one dimensional numerical indexed array with the
	 * Elements, SubForms and Elements from DisplayGroups as Values.
	 *
	 * Subitems are inserted based on their order Setting if set,
	 * otherwise they are appended, the resulting numerical index
	 * may differ from the order value.
	 *
	 * @access protected
	 * @return array
	 */
	public function getElementsAndSubFormsOrdered() {
		$ordered = array ();
		foreach ( $this->_order as $name => $order ) {
			$order = isset ( $order ) ? $order : count ( $ordered );
			if ($this->$name instanceof Zend_Form_Element || $this->$name instanceof Zend_Form) {
				array_splice ( $ordered, $order, 0, array ($this->$name ) );
			} else if ($this->$name instanceof Zend_Form_DisplayGroup) {
				$subordered = array ();
				foreach ( $this->$name->getElements () as $element ) {
					$suborder = $element->getOrder ();
					$suborder = (null !== $suborder) ? $suborder : count ( $subordered );
					array_splice ( $subordered, $suborder, 0, array ($element ) );
				}
				if (! empty ( $subordered )) {
					array_splice ( $ordered, $order, 0, $subordered );
				}
			}
		}
		return $ordered;
	}
	
	/**
	 * This is a helper function until php 5.3 is widespreaded
	 *
	 * @param array $into
	 * @access protected
	 * @return void
	 */
	protected function _array_replace_recursive(array $into) {
		$fromArrays = array_slice ( func_get_args (), 1 );
		
		foreach ( $fromArrays as $from ) {
			foreach ( $from as $key => $value ) {
				if (is_array ( $value )) {
					if (! isset ( $into [$key] )) {
						$into [$key] = array ();
					}
					$into [$key] = $this->_array_replace_recursive ( $into [$key], $from [$key] );
				} else {
					$into [$key] = $value;
				}
			}
		}
		return $into;
	}
	
	/**
	 * Validate the form
	 *
	 * @param  array $data
	 * @return boolean
	 */
	public function isValid($data) {
		if (! is_array ( $data )) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( __METHOD__ . ' expects an array' );
		}
		$translator = $this->getTranslator ();
		$valid = true;
		$eBelongTo = null;
		
		if ($this->isArray ()) {
			$eBelongTo = $this->getElementsBelongTo ();
			$data = $this->_dissolveArrayValue ( $data, $eBelongTo );
		}
		$context = $data;
		foreach ( $this->getElements () as $key => $element ) {
			if (null !== $translator && $this->hasTranslator () && ! $element->hasTranslator ()) {
				$element->setTranslator ( $translator );
			}
			$check = $data;
			if (($belongsTo = $element->getBelongsTo ()) !== $eBelongTo) {
				$check = $this->_dissolveArrayValue ( $data, $belongsTo );
			}
			if (! isset ( $check [$key] )) {
				$valid = $element->isValid ( null, $context ) && $valid;
			} else {
				$valid = $element->isValid ( $check [$key], $context ) && $valid;
				$data = $this->_dissolveArrayUnsetKey ( $data, $belongsTo, $key );
			}
		}
		foreach ( $this->getSubForms () as $key => $form ) {
			if (null !== $translator && ! $form->hasTranslator ()) {
				$form->setTranslator ( $translator );
			}
			if (isset ( $data [$key] ) && ! $form->isArray ()) {
				$valid = $form->isValid ( $data [$key] ) && $valid;
			} else {
				$valid = $form->isValid ( $data ) && $valid;
			}
		}
		
		$this->_errorsExist = ! $valid;
		
		// If manually flagged as an error, return invalid status
		if ($this->_errorsForced) {
			return false;
		}
		
		return $valid;
	}
	
	/**
	 * Validate a partial form
	 *
	 * Does not check for required flags.
	 *
	 * @param  array $data
	 * @return boolean
	 */
	public function isValidPartial(array $data) {
		$eBelongTo = null;
		
		if ($this->isArray ()) {
			$eBelongTo = $this->getElementsBelongTo ();
			$data = $this->_dissolveArrayValue ( $data, $eBelongTo );
		}
		
		$translator = $this->getTranslator ();
		$valid = true;
		$context = $data;
		
		foreach ( $this->getElements () as $key => $element ) {
			$check = $data;
			if (($belongsTo = $element->getBelongsTo ()) !== $eBelongTo) {
				$check = $this->_dissolveArrayValue ( $data, $belongsTo );
			}
			if (isset ( $check [$key] )) {
				if (null !== $translator && ! $element->hasTranslator ()) {
					$element->setTranslator ( $translator );
				}
				$valid = $element->isValid ( $check [$key], $context ) && $valid;
				$data = $this->_dissolveArrayUnsetKey ( $data, $belongsTo, $key );
			}
		}
		foreach ( $this->getSubForms () as $key => $form ) {
			if (null !== $translator && ! $form->hasTranslator ()) {
				$form->setTranslator ( $translator );
			}
			if (isset ( $data [$key] ) && ! $form->isArray ()) {
				$valid = $form->isValidPartial ( $data [$key] ) && $valid;
			} else {
				$valid = $form->isValidPartial ( $data ) && $valid;
			}
		}
		
		$this->_errorsExist = ! $valid;
		return $valid;
	}
	
	/**
	 * Process submitted AJAX data
	 *
	 * Checks if provided $data is valid, via {@link isValidPartial()}. If so,
	 * it returns JSON-encoded boolean true. If not, it returns JSON-encoded
	 * error messages (as returned by {@link getMessages()}).
	 *
	 * @param  array $data
	 * @return string JSON-encoded boolean true or error messages
	 */
	public function processAjax(array $data) {
		require_once 'Zend/Json.php';
		if ($this->isValidPartial ( $data )) {
			return Zend_Json::encode ( true );
		}
		$messages = $this->getMessages ();
		return Zend_Json::encode ( $messages );
	}
	
	/**
	 * Add a custom error message to return in the event of failed validation
	 *
	 * @param  string $message
	 * @return Zend_Form
	 */
	public function addErrorMessage($message) {
		$this->_errorMessages [] = ( string ) $message;
		return $this;
	}
	
	/**
	 * Add multiple custom error messages to return in the event of failed validation
	 *
	 * @param  array $messages
	 * @return Zend_Form
	 */
	public function addErrorMessages(array $messages) {
		foreach ( $messages as $message ) {
			$this->addErrorMessage ( $message );
		}
		return $this;
	}
	
	/**
	 * Same as addErrorMessages(), but clears custom error message stack first
	 *
	 * @param  array $messages
	 * @return Zend_Form
	 */
	public function setErrorMessages(array $messages) {
		$this->clearErrorMessages ();
		return $this->addErrorMessages ( $messages );
	}
	
	/**
	 * Retrieve custom error messages
	 *
	 * @return array
	 */
	public function getErrorMessages() {
		return $this->_errorMessages;
	}
	
	/**
	 * Clear custom error messages stack
	 *
	 * @return Zend_Form
	 */
	public function clearErrorMessages() {
		$this->_errorMessages = array ();
		return $this;
	}
	
	/**
	 * Mark the element as being in a failed validation state
	 *
	 * @return Zend_Form
	 */
	public function markAsError() {
		$this->_errorsExist = true;
		$this->_errorsForced = true;
		return $this;
	}
	
	/**
	 * Add an error message and mark element as failed validation
	 *
	 * @param  string $message
	 * @return Zend_Form
	 */
	public function addError($message) {
		$this->addErrorMessage ( $message );
		$this->markAsError ();
		return $this;
	}
	
	/**
	 * Add multiple error messages and flag element as failed validation
	 *
	 * @param  array $messages
	 * @return Zend_Form
	 */
	public function addErrors(array $messages) {
		foreach ( $messages as $message ) {
			$this->addError ( $message );
		}
		return $this;
	}
	
	/**
	 * Overwrite any previously set error messages and flag as failed validation
	 *
	 * @param  array $messages
	 * @return Zend_Form
	 */
	public function setErrors(array $messages) {
		$this->clearErrorMessages ();
		return $this->addErrors ( $messages );
	}
	
	public function persistData() {
	}
	
	/**
	 * Are there errors in the form?
	 *
	 * @return bool
	 */
	public function isErrors() {
		return $this->_errorsExist;
	}
	
	/**
	 * Get error codes for all elements failing validation
	 *
	 * @param  string $name
	 * @return array
	 */
	public function getErrors($name = null, $suppressArrayNotation = false) {
		$errors = array ();
		if (null !== $name) {
			if (isset ( $this->_elements [$name] )) {
				return $this->getElement ( $name )->getErrors ();
			} else if (isset ( $this->_subForms [$name] )) {
				return $this->getSubForm ( $name )->getErrors ( null, true );
			}
		}
		
		foreach ( $this->_elements as $key => $element ) {
			$errors [$key] = $element->getErrors ();
		}
		foreach ( $this->getSubForms () as $key => $subForm ) {
			$merge = array ();
			if (! $subForm->isArray ()) {
				$merge [$key] = $subForm->getErrors ();
			} else {
				$merge = $this->_attachToArray ( $subForm->getErrors ( null, true ), $subForm->getElementsBelongTo () );
			}
			$errors = $this->_array_replace_recursive ( $errors, $merge );
		}
		
		if (! $suppressArrayNotation && $this->isArray () && ! $this->_getIsRendered ()) {
			$errors = $this->_attachToArray ( $errors, $this->getElementsBelongTo () );
		}
		
		return $errors;
	}
	
	/**
	 * Retrieve error messages from elements failing validations
	 *
	 * @param  string $name
	 * @param  bool $suppressArrayNotation
	 * @return array
	 */
	public function getMessages($name = null, $suppressArrayNotation = false) {
		if (null !== $name) {
			if (isset ( $this->_elements [$name] )) {
				return $this->getElement ( $name )->getMessages ();
			} else if (isset ( $this->_subForms [$name] )) {
				return $this->getSubForm ( $name )->getMessages ( null, true );
			}
			foreach ( $this->getSubForms () as $key => $subForm ) {
				if ($subForm->isArray ()) {
					$belongTo = $subForm->getElementsBelongTo ();
					if ($name == $this->_getArrayName ( $belongTo )) {
						return $subForm->getMessages ( null, true );
					}
				}
			}
		}
		
		$customMessages = $this->_getErrorMessages ();
		if ($this->isErrors () && ! empty ( $customMessages )) {
			return $customMessages;
		}
		
		$messages = array ();
		
		foreach ( $this->getElements () as $name => $element ) {
			$eMessages = $element->getMessages ();
			if (! empty ( $eMessages )) {
				$messages [$name] = $eMessages;
			}
		}
		
		foreach ( $this->getSubForms () as $key => $subForm ) {
			$merge = $subForm->getMessages ( null, true );
			if (! empty ( $merge )) {
				if (! $subForm->isArray ()) {
					$merge = array ($key => $merge );
				} else {
					$merge = $this->_attachToArray ( $merge, $subForm->getElementsBelongTo () );
				}
				$messages = $this->_array_replace_recursive ( $messages, $merge );
			}
		}
		
		if (! $suppressArrayNotation && $this->isArray () && ! $this->_getIsRendered ()) {
			$messages = $this->_attachToArray ( $messages, $this->getElementsBelongTo () );
		}
		
		return $messages;
	}
	
	/**
	 * Retrieve translated custom error messages
	 * Proxies to {@link _getErrorMessages()}.
	 *
	 * @return array
	 */
	public function getCustomMessages() {
		return $this->_getErrorMessages ();
	}
	
	// Rendering
	

	/**
	 * Set view object
	 *
	 * @param  Zend_View_Interface $view
	 * @return Zend_Form
	 */
	public function setView(Zend_View_Interface $view = null) {
		$this->_view = $view;
		return $this;
	}
	
	/**
	 * Retrieve view object
	 *
	 * If none registered, attempts to pull from ViewRenderer.
	 *
	 * @return Zend_View_Interface|null
	 */
	public function getView() {
		if (null === $this->_view) {
			require_once 'Zend/Controller/Action/HelperBroker.php';
			$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper ( 'viewRenderer' );
			$this->setView ( $viewRenderer->view );
		}
		
		return $this->_view;
	}
	
	/**
	 * Instantiate a decorator based on class name or class name fragment
	 *
	 * @param  string $name
	 * @param  null|array $options
	 * @return Zend_Form_Decorator_Interface
	 */
	protected function _getDecorator($name, $options) {
		$class = $this->getPluginLoader ( self::DECORATOR )->load ( $name );
		if (null === $options) {
			$decorator = new $class ();
		} else {
			$decorator = new $class ( $options );
		}
		
		return $decorator;
	}
	
	/**
	 * Add a decorator for rendering the element
	 *
	 * @param  string|Zend_Form_Decorator_Interface $decorator
	 * @param  array|Zend_Config $options Options with which to initialize decorator
	 * @return Zend_Form
	 */
	public function addDecorator($decorator, $options = null) {
		if ($decorator instanceof Zend_Form_Decorator_Interface) {
			$name = get_class ( $decorator );
		} elseif (is_string ( $decorator )) {
			$name = $decorator;
			$decorator = array ('decorator' => $name, 'options' => $options );
		} elseif (is_array ( $decorator )) {
			foreach ( $decorator as $name => $spec ) {
				break;
			}
			if (is_numeric ( $name )) {
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception ( 'Invalid alias provided to addDecorator; must be alphanumeric string' );
			}
			if (is_string ( $spec )) {
				$decorator = array ('decorator' => $spec, 'options' => $options );
			} elseif ($spec instanceof Zend_Form_Decorator_Interface) {
				$decorator = $spec;
			}
		} else {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( 'Invalid decorator provided to addDecorator; must be string or Zend_Form_Decorator_Interface' );
		}
		
		$this->_decorators [$name] = $decorator;
		
		return $this;
	}
	
	/**
	 * Add many decorators at once
	 *
	 * @param  array $decorators
	 * @return Zend_Form
	 */
	public function addDecorators(array $decorators) {
		foreach ( $decorators as $decoratorName => $decoratorInfo ) {
			if (is_string ( $decoratorInfo ) || $decoratorInfo instanceof Zend_Form_Decorator_Interface) {
				if (! is_numeric ( $decoratorName )) {
					$this->addDecorator ( array ($decoratorName => $decoratorInfo ) );
				} else {
					$this->addDecorator ( $decoratorInfo );
				}
			} elseif (is_array ( $decoratorInfo )) {
				$argc = count ( $decoratorInfo );
				$options = array ();
				if (isset ( $decoratorInfo ['decorator'] )) {
					$decorator = $decoratorInfo ['decorator'];
					if (isset ( $decoratorInfo ['options'] )) {
						$options = $decoratorInfo ['options'];
					}
					$this->addDecorator ( $decorator, $options );
				} else {
					switch (true) {
						case (0 == $argc) :
							break;
						case (1 <= $argc) :
							$decorator = array_shift ( $decoratorInfo );
						case (2 <= $argc) :
							$options = array_shift ( $decoratorInfo );
						default :
							$this->addDecorator ( $decorator, $options );
							break;
					}
				}
			} else {
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception ( 'Invalid decorator passed to addDecorators()' );
			}
		}
		
		return $this;
	}
	
	/**
	 * Overwrite all decorators
	 *
	 * @param  array $decorators
	 * @return Zend_Form
	 */
	public function setDecorators(array $decorators) {
		$this->clearDecorators ();
		return $this->addDecorators ( $decorators );
	}
	
	/**
	 * Retrieve a registered decorator
	 *
	 * @param  string $name
	 * @return false|Zend_Form_Decorator_Abstract
	 */
	public function getDecorator($name) {
		if (! isset ( $this->_decorators [$name] )) {
			$len = strlen ( $name );
			foreach ( $this->_decorators as $localName => $decorator ) {
				if ($len > strlen ( $localName )) {
					continue;
				}
				
				if (0 === substr_compare ( $localName, $name, - $len, $len, true )) {
					if (is_array ( $decorator )) {
						return $this->_loadDecorator ( $decorator, $localName );
					}
					return $decorator;
				}
			}
			return false;
		}
		
		if (is_array ( $this->_decorators [$name] )) {
			return $this->_loadDecorator ( $this->_decorators [$name], $name );
		}
		
		return $this->_decorators [$name];
	}
	
	/**
	 * Retrieve all decorators
	 *
	 * @return array
	 */
	public function getDecorators() {
		foreach ( $this->_decorators as $key => $value ) {
			if (is_array ( $value )) {
				$this->_loadDecorator ( $value, $key );
			}
		}
		return $this->_decorators;
	}
	
	/**
	 * Remove a single decorator
	 *
	 * @param  string $name
	 * @return bool
	 */
	public function removeDecorator($name) {
		$decorator = $this->getDecorator ( $name );
		if ($decorator) {
			if (array_key_exists ( $name, $this->_decorators )) {
				unset ( $this->_decorators [$name] );
			} else {
				$class = get_class ( $decorator );
				if (! array_key_exists ( $class, $this->_decorators )) {
					return false;
				}
				unset ( $this->_decorators [$class] );
			}
			return true;
		}
		
		return false;
	}
	
	/**
	 * Clear all decorators
	 *
	 * @return Zend_Form
	 */
	public function clearDecorators() {
		$this->_decorators = array ();
		return $this;
	}
	
	/**
	 * Set all element decorators as specified
	 *
	 * @param  array $decorators
	 * @param  array|null $elements Specific elements to decorate or exclude from decoration
	 * @param  bool $include Whether $elements is an inclusion or exclusion list
	 * @return Zend_Form
	 */
	public function setElementDecorators(array $decorators, array $elements = null, $include = true) {
		if (is_array ( $elements )) {
			if ($include) {
				$elementObjs = array ();
				foreach ( $elements as $name ) {
					if (null !== ($element = $this->getElement ( $name ))) {
						$elementObjs [] = $element;
					}
				}
			} else {
				$elementObjs = $this->getElements ();
				foreach ( $elements as $name ) {
					if (array_key_exists ( $name, $elementObjs )) {
						unset ( $elementObjs [$name] );
					}
				}
			}
		} else {
			$elementObjs = $this->getElements ();
		}
		
		foreach ( $elementObjs as $element ) {
			$element->setDecorators ( $decorators );
		}
		
		$this->_elementDecorators = $decorators;
		
		return $this;
	}
	
	/**
	 * Set all display group decorators as specified
	 *
	 * @param  array $decorators
	 * @return Zend_Form
	 */
	public function setDisplayGroupDecorators(array $decorators) {
		foreach ( $this->getDisplayGroups () as $group ) {
			$group->setDecorators ( $decorators );
		}
		
		return $this;
	}
	
	/**
	 * Set all subform decorators as specified
	 *
	 * @param  array $decorators
	 * @return Zend_Form
	 */
	public function setSubFormDecorators(array $decorators) {
		foreach ( $this->getSubForms () as $form ) {
			$form->setDecorators ( $decorators );
		}
		
		return $this;
	}
	
	/**
	 * Render form
	 *
	 * @param  Zend_View_Interface $view
	 * @return string
	 */
	public function render(Zend_View_Interface $view = null) {
		if (null !== $view) {
			$this->setView ( $view );
		}
		
		$content = '';
		foreach ( $this->getDecorators () as $decorator ) {
			$decorator->setElement ( $this );
			$content = $decorator->render ( $content );
		}
		$this->_setIsRendered ();
		return $content;
	}
	
	/**
	 * Serialize as string
	 *
	 * Proxies to {@link render()}.
	 *
	 * @return string
	 */
	public function __toString() {
		try {
			$return = $this->render ();
			return $return;
		} catch ( Exception $e ) {
			$message = "Exception caught by form: " . $e->getMessage () . "\nStack Trace:\n" . $e->getTraceAsString ();
			trigger_error ( $message, E_USER_WARNING );
			return '';
		}
	}
	
	// Localization:
	

	/**
	 * Set translator object
	 *
	 * @param  Zend_Translate|Zend_Translate_Adapter|null $translator
	 * @return Zend_Form
	 */
	public function setTranslator($translator = null) {
		if (null === $translator) {
			$this->_translator = null;
		} elseif ($translator instanceof Zend_Translate_Adapter) {
			$this->_translator = $translator;
		} elseif ($translator instanceof Zend_Translate) {
			$this->_translator = $translator->getAdapter ();
		} else {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( 'Invalid translator specified' );
		}
		
		return $this;
	}
	
	/**
	 * Set global default translator object
	 *
	 * @param  Zend_Translate|Zend_Translate_Adapter|null $translator
	 * @return void
	 */
	public static function setDefaultTranslator($translator = null) {
		if (null === $translator) {
			self::$_translatorDefault = null;
		} elseif ($translator instanceof Zend_Translate_Adapter) {
			self::$_translatorDefault = $translator;
		} elseif ($translator instanceof Zend_Translate) {
			self::$_translatorDefault = $translator->getAdapter ();
		} else {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( 'Invalid translator specified' );
		}
	}
	
	/**
	 * Retrieve translator object
	 *
	 * @return Zend_Translate|null
	 */
	public function getTranslator() {
		if ($this->translatorIsDisabled ()) {
			return null;
		}
		
		if (null === $this->_translator) {
			return self::getDefaultTranslator ();
		}
		
		return $this->_translator;
	}
	
	/**
	 * Does this form have its own specific translator?
	 *
	 * @return bool
	 */
	public function hasTranslator() {
		return ( bool ) $this->_translator;
	}
	
	/**
	 * Get global default translator object
	 *
	 * @return null|Zend_Translate
	 */
	public static function getDefaultTranslator() {
		if (null === self::$_translatorDefault) {
			require_once 'Zend/Registry.php';
			if (Zend_Registry::isRegistered ( 'Zend_Translate' )) {
				$translator = Zend_Registry::get ( 'Zend_Translate' );
				if ($translator instanceof Zend_Translate_Adapter) {
					return $translator;
				} elseif ($translator instanceof Zend_Translate) {
					return $translator->getAdapter ();
				}
			}
		}
		return self::$_translatorDefault;
	}
	
	/**
	 * Is there a default translation object set?
	 *
	 * @return boolean
	 */
	public static function hasDefaultTranslator() {
		return ( bool ) self::$_translatorDefault;
	}
	
	/**
	 * Indicate whether or not translation should be disabled
	 *
	 * @param  bool $flag
	 * @return Zend_Form
	 */
	public function setDisableTranslator($flag) {
		$this->_translatorDisabled = ( bool ) $flag;
		return $this;
	}
	
	/**
	 * Is translation disabled?
	 *
	 * @return bool
	 */
	public function translatorIsDisabled() {
		return $this->_translatorDisabled;
	}
	
	/**
	 * Overloading: access to elements, form groups, and display groups
	 *
	 * @param  string $name
	 * @return Zend_Form_Element|Zend_Form|null
	 */
	public function __get($name) {
		if (isset ( $this->_elements [$name] )) {
			return $this->_elements [$name];
		} elseif (isset ( $this->_subForms [$name] )) {
			return $this->_subForms [$name];
		} elseif (isset ( $this->_displayGroups [$name] )) {
			return $this->_displayGroups [$name];
		}
		
		return null;
	}
	
	/**
	 * Overloading: access to elements, form groups, and display groups
	 *
	 * @param  string $name
	 * @param  Zend_Form_Element|Zend_Form $value
	 * @return void
	 * @throws Zend_Form_Exception for invalid $value
	 */
	public function __set($name, $value) {
		if ($value instanceof Zend_Form_Element) {
			$this->addElement ( $value, $name );
			return;
		} elseif ($value instanceof Zend_Form) {
			$this->addSubForm ( $value, $name );
			return;
		} elseif (is_array ( $value )) {
			$this->addDisplayGroup ( $value, $name );
			return;
		}
		
		require_once 'Zend/Form/Exception.php';
		if (is_object ( $value )) {
			$type = get_class ( $value );
		} else {
			$type = gettype ( $value );
		}
		throw new Zend_Form_Exception ( 'Only form elements and groups may be overloaded; variable of type "' . $type . '" provided' );
	}
	
	/**
	 * Overloading: access to elements, form groups, and display groups
	 *
	 * @param  string $name
	 * @return boolean
	 */
	public function __isset($name) {
		if (isset ( $this->_elements [$name] ) || isset ( $this->_subForms [$name] ) || isset ( $this->_displayGroups [$name] )) {
			return true;
		}
		
		return false;
	}
	
	/**
	 * Overloading: access to elements, form groups, and display groups
	 *
	 * @param  string $name
	 * @return void
	 */
	public function __unset($name) {
		if (isset ( $this->_elements [$name] )) {
			unset ( $this->_elements [$name] );
		} elseif (isset ( $this->_subForms [$name] )) {
			unset ( $this->_subForms [$name] );
		} elseif (isset ( $this->_displayGroups [$name] )) {
			unset ( $this->_displayGroups [$name] );
		}
	}
	
	/**
	 * Overloading: allow rendering specific decorators
	 *
	 * Call renderDecoratorName() to render a specific decorator.
	 *
	 * @param  string $method
	 * @param  array $args
	 * @return string
	 * @throws Zend_Form_Exception for invalid decorator or invalid method call
	 */
	public function __call($method, $args) {
		if ('render' == substr ( $method, 0, 6 )) {
			$decoratorName = substr ( $method, 6 );
			if (false !== ($decorator = $this->getDecorator ( $decoratorName ))) {
				$decorator->setElement ( $this );
				$seed = '';
				if (0 < count ( $args )) {
					$seed = array_shift ( $args );
				}
				if ($decoratorName === 'FormElements' || $decoratorName === 'PrepareElements') {
					$this->_setIsRendered ();
				}
				return $decorator->render ( $seed );
			}
			
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( sprintf ( 'Decorator by name %s does not exist', $decoratorName ) );
		}
		
		require_once 'Zend/Form/Exception.php';
		throw new Zend_Form_Exception ( sprintf ( 'Method %s does not exist', $method ) );
	}
	
	// Interfaces: Iterator, Countable
	

	/**
	 * Current element/subform/display group
	 *
	 * @return Zend_Form_Element|Zend_Form_DisplayGroup|Zend_Form
	 */
	public function current() {
		$this->_sort ();
		current ( $this->_order );
		$key = key ( $this->_order );
		
		if (isset ( $this->_elements [$key] )) {
			return $this->getElement ( $key );
		} elseif (isset ( $this->_subForms [$key] )) {
			return $this->getSubForm ( $key );
		} elseif (isset ( $this->_displayGroups [$key] )) {
			return $this->getDisplayGroup ( $key );
		} else {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( sprintf ( 'Corruption detected in form; invalid key ("%s") found in internal iterator', ( string ) $key ) );
		}
	}
	
	/**
	 * Current element/subform/display group name
	 *
	 * @return string
	 */
	public function key() {
		$this->_sort ();
		return key ( $this->_order );
	}
	
	/**
	 * Move pointer to next element/subform/display group
	 *
	 * @return void
	 */
	public function next() {
		$this->_sort ();
		next ( $this->_order );
	}
	
	/**
	 * Move pointer to beginning of element/subform/display group loop
	 *
	 * @return void
	 */
	public function rewind() {
		$this->_sort ();
		reset ( $this->_order );
	}
	
	/**
	 * Determine if current element/subform/display group is valid
	 *
	 * @return bool
	 */
	public function valid() {
		$this->_sort ();
		return (current ( $this->_order ) !== false);
	}
	
	/**
	 * Count of elements/subforms that are iterable
	 *
	 * @return int
	 */
	public function count() {
		return count ( $this->_order );
	}
	
	/**
	 * Set flag to disable loading default decorators
	 *
	 * @param  bool $flag
	 * @return Zend_Form
	 */
	public function setDisableLoadDefaultDecorators($flag) {
		$this->_disableLoadDefaultDecorators = ( bool ) $flag;
		return $this;
	}
	
	/**
	 * Should we load the default decorators?
	 *
	 * @return bool
	 */
	public function loadDefaultDecoratorsIsDisabled() {
		return $this->_disableLoadDefaultDecorators;
	}
	
	/**
	 * Load the default decorators
	 *
	 * @return Zend_Form
	 */
	public function loadDefaultDecorators() {
		if ($this->loadDefaultDecoratorsIsDisabled ()) {
			return $this;
		}
		
		$decorators = $this->getDecorators ();
		if (empty ( $decorators )) {
			$this->addDecorator ( 'FormElements' )->addDecorator ( 'HtmlTag', array ('tag' => 'dl', 'class' => 'zend_form' ) )->addDecorator ( 'Form' );
		}
		return $this;
	}
	
	/**
	 * Remove an element from iteration
	 *
	 * @param  string $name Element/group/form name
	 * @return void
	 */
	public function removeFromIteration($name) {
		if (array_key_exists ( $name, $this->_order )) {
			unset ( $this->_order [$name] );
			$this->_orderUpdated = true;
		}
	}
	
	/**
	 * Sort items according to their order
	 *
	 * @return void
	 */
	protected function _sort() {
		if ($this->_orderUpdated) {
			$items = array ();
			$index = 0;
			foreach ( $this->_order as $key => $order ) {
				if (null === $order) {
					if (null === ($order = $this->{$key}->getOrder ())) {
						while ( array_search ( $index, $this->_order, true ) ) {
							++ $index;
						}
						$items [$index] = $key;
						++ $index;
					} else {
						$items [$order] = $key;
					}
				} else {
					$items [$order] = $key;
				}
			}
			
			$items = array_flip ( $items );
			asort ( $items );
			$this->_order = $items;
			$this->_orderUpdated = false;
		}
	}
	
	/**
	 * Lazy-load a decorator
	 *
	 * @param  array $decorator Decorator type and options
	 * @param  mixed $name Decorator name or alias
	 * @return Zend_Form_Decorator_Interface
	 */
	protected function _loadDecorator(array $decorator, $name) {
		$sameName = false;
		if ($name == $decorator ['decorator']) {
			$sameName = true;
		}
		
		$instance = $this->_getDecorator ( $decorator ['decorator'], $decorator ['options'] );
		if ($sameName) {
			$newName = get_class ( $instance );
			$decoratorNames = array_keys ( $this->_decorators );
			$order = array_flip ( $decoratorNames );
			$order [$newName] = $order [$name];
			$decoratorsExchange = array ();
			unset ( $order [$name] );
			asort ( $order );
			foreach ( $order as $key => $index ) {
				if ($key == $newName) {
					$decoratorsExchange [$key] = $instance;
					continue;
				}
				$decoratorsExchange [$key] = $this->_decorators [$key];
			}
			$this->_decorators = $decoratorsExchange;
		} else {
			$this->_decorators [$name] = $instance;
		}
		
		return $instance;
	}
	
	/**
	 * Retrieve optionally translated custom error messages
	 *
	 * @return array
	 */
	protected function _getErrorMessages() {
		$messages = $this->getErrorMessages ();
		$translator = $this->getTranslator ();
		if (null !== $translator) {
			foreach ( $messages as $key => $message ) {
				$messages [$key] = $translator->translate ( $message );
			}
		}
		return $messages;
	}
}
